#include "websocket.h"
#include "biglist.h"
#include "test.h"
#include "Debug.h"
#include "datastring.h"
#include "datablock.h"
#include <cstdint>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

// Ok. Maybe 2 global variables. Shhhh. Don't tell anyone.
websocket *the_websocket = nullptr;
struct payload received_payload;

websocket::websocket()
{
	server_password = nullptr;
	chatroom_tasks = new tasks();
	next_chatclientid = 1;
	next_chatroomid = 1;
	shutdown = false;
	run_async = true;  // Set to false for testing.
}
websocket::~websocket()
{
	if (server_password != nullptr) {
		delete server_password;
		server_password = nullptr;
	}	
	users.clear(true);
	chatrooms.clear(true);
	chatclients.clear(true);
	simplechatgames.deleteallitems();
	delete chatroom_tasks;
	chatroom_tasks = nullptr;
}

biglist_item<simplechatgame *> *websocket::find_simple_game(datastring gameid)
{
	biglist_iterator<simplechatgame *>loop(&simplechatgames);
	while(!loop.eof()) {
		if (gameid == *(loop.item->gameid)) {
			return loop.row;
		}
		loop.movenext();
	}
	return nullptr;
}

biglist_item<simplechatgame *> *websocket::add_simple_game(simplechatgame *game)
{
	biglist_item<simplechatgame *> *item;
	item = simplechatgames.add(game);
	if (item != nullptr) {
		game->clone();
	}
	return item;
}

void websocket::remove_client_from_simple_games(chatclient *client,bool senduserlistmessage)
{
	DEBUG_FUNCTION
	simplechatgame *game;
	biglist_item<simplechatgameuser *> *loop2;
	biglist_iterator<simplechatgame *> loop1(&simplechatgames);
	DEBUG_LINE
	while (!loop1.eof()) {
		DEBUG_LINE
		game = loop1.item;
		DEBUG_LINE
		loop2 = game->getuser(client);
		DEBUG_LINE
		if (loop2 != nullptr) {
			DEBUG_LINE
			if (loop2->item->client != nullptr) {
				DEBUG_LINE
				loop2->item->client = nullptr; // Don't remove the user. Just set the client to null. They might rejoin.
				DEBUG_LINE
				if (game->usercount(true) == 0) {
					// This game has no connected users. Delete the game.
					DEBUG_LINE
					loop1.row->used = false;
					DEBUG_LINE
					idisposable::dereference((idisposable**)&game);
					DEBUG_LINE
					game = nullptr;
				} else {
					DEBUG_LINE
					game->send_user_list_to_clients();
					DEBUG_LINE
				}
			}
		}
		DEBUG_LINE
		loop1.movenext();
	}
	DEBUG_LINE
}

int websocket::callback_http( struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len )
{
	switch( reason )
	{
		case LWS_CALLBACK_HTTP:
			lws_serve_http_file( wsi, "example.html", "text/html", NULL, 0 );
			break;
		default:
			break;
	}

	return 0;
}

int websocket::callback_chatroom( struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len )
{
	const int CLOSE_CONNECTION = -1;
	Debug debug(__FILE__,__func__,__LINE__);
	chatclient *client = (chatclient *)user;	
	task *task_item;
	message *new_message;
	int return_value = 0;
	
	debug = __LINE__;
	switch( reason )
	{
		case LWS_CALLBACK_ESTABLISHED:
			// (VH) after the server completes a handshake with an incoming
			// client.  If you built the library with ssl support, in is a
			// pointer to the ssl struct associated with the connection or NULL.
			//printf("LWS_CALLBACK_ESTABLISHED\n");
			debug = __LINE__;
			//if (client->wsi != wsi) {
				client->initialize(wsi,the_websocket->next_chatclientid++);
				the_websocket->chatclients.add(client);
				client->send_yourchatclientid();
			//}
			debug = __LINE__;
			break;
		case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
			//printf("LWS_CALLBACK_CLIENT_CONNECTION_ERROR\n");
			//datastring error;
			//error.data = (char *)in;
			//error.length = len;
			//error.println();
			
			break;
		case LWS_CALLBACK_CLIENT_ESTABLISHED:
			// after your client connection completed
			// a handshake with the remote server.
			// Your client connection is actually active 
			// only when you receive LWS_CALLBACK_CLIENT_ESTABLISHED for it.
			//printf("LWS_CALLBACK_CLIENT_ESTABLISHED\n");
			debug = __LINE__;
			/*if (client->wsi != wsi) {
				printf("Initialized\n");
				client->initialize(wsi,the_websocket->next_chatclientid++);
				the_websocket->chatclients.add(client);
				client->send_yourchatclientid();
			}
			debug = __LINE__;*/
			break;
			
		case LWS_CALLBACK_CLOSED:
			// The client closed the connection.
			debug = __LINE__;
			task_item = new task();	
			task_item->closeconnection(client,the_websocket->run_async);			
			the_websocket->chatroom_tasks->add_task(task_item);
			task_item = nullptr;
			if (!the_websocket->run_async) {
				the_websocket->chatroom_tasks->do_tasks();
			}
			debug = __LINE__;
			break;
			
		case LWS_CALLBACK_RECEIVE:
			// The server received data from a client.
			debug = __LINE__;
			if (the_websocket->shutdown) {
				// The server is shutting down. Start closing client connections. Ignore future client requests.
				client->should_disconnect = true;
				the_websocket->remove_client_from_simple_games(client,false);
			} else if (client->wsi != nullptr) {
				new_message = new message();
				if (!new_message->set((char *)in, len)) {
					printf("Memory error.\n");
				} else {
					task_item = new task();				
					task_item->receivedmessage(client,new_message,the_websocket->run_async);
					debug = __LINE__;
					the_websocket->chatroom_tasks->add_task(task_item);
					debug = __LINE__;
					if (!the_websocket->run_async) {
						the_websocket->chatroom_tasks->do_tasks();
					}
				}
				idisposable::dereference((idisposable**)&new_message);
			}
			debug = __LINE__;
			//lws_callback_on_writable_all_protocol( lws_get_context( wsi ), lws_get_protocol( wsi ) );
			break;

		case LWS_CALLBACK_SERVER_WRITEABLE:
			//printf("LWS_CALLBACK_SERVER_WRITEABLE\n");
			// Try to get a message. Send it if it's available.
			debug = __LINE__;
			if (client->wsi != nullptr) {
				new_message = client->get_next_message();
				if (new_message == nullptr) {
					if (client->should_disconnect) {
						return_value = CLOSE_CONNECTION;
					}
				} else {
					// Send the message.
					debug = __LINE__;
					lws_write( wsi, (unsigned char *)new_message->actual_message.data,new_message->actual_message.length,LWS_WRITE_TEXT);
					debug = __LINE__;
					idisposable::dereference((idisposable**)&new_message);
					// If there are more messages, send a request to send another message.
					if ((client->should_disconnect) 
					|| ((client->messages_to_send != nullptr)
					&& (!client->messages_to_send->empty()))) {
						lws_callback_on_writable(client->wsi);
					}
				}
			}
			debug = __LINE__;
			break;
			
		default:
			break;
	}
	// When you want to close a connection, you do it by returning -1 from a callback for that connection.
	return return_value;
}

int websocket::callback_example( struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len )
{	
	Debug debug(__FILE__,__func__,__LINE__);
	switch( reason )
	{
		case LWS_CALLBACK_CLIENT_ESTABLISHED:
			// after your client connection completed
			// a handshake with the remote server.
			// Your client connection is actually active 
			// only when you receive LWS_CALLBACK_CLIENT_ESTABLISHED for it.
			break;
		case LWS_CALLBACK_ESTABLISHED:
			// (VH) after the server completes a handshake with an incoming
			// client.  If you built the library with ssl support, in is a
			// pointer to the ssl struct associated with the connection or NULL.*/			 
			break;
		case LWS_CALLBACK_CLOSED: 
			// when the websocket session ends			
			break;
		case LWS_CALLBACK_RECEIVE:
			// data has appeared for this server endpoint from a
			// remote client, it can be found at *in and is
			// len bytes long 
			debug = __LINE__;
			memcpy( &received_payload.data[LWS_SEND_BUFFER_PRE_PADDING], in, len );
			received_payload.len = len;
			lws_callback_on_writable_all_protocol( lws_get_context( wsi ), lws_get_protocol( wsi ) );
			debug = __LINE__;
			break;
		case LWS_CALLBACK_SERVER_WRITEABLE:
			//  If you call lws_callback_on_writable() on a connection, you will
			// get one of these callbacks coming when the connection socket
			// is able to accept another write packet without blocking.
			// If it already was able to take another packet without blocking,
			// you'll get this callback at the next call to the service loop
			// function.  Notice that CLIENTs get LWS_CALLBACK_CLIENT_WRITEABLE
			// and servers get LWS_CALLBACK_SERVER_WRITEABLE.
			debug = __LINE__;
			lws_write( wsi, &received_payload.data[LWS_SEND_BUFFER_PRE_PADDING], received_payload.len, LWS_WRITE_TEXT );
			debug = __LINE__;
			break;
		case LWS_CALLBACK_PROTOCOL_INIT:
			// One-time call per protocol, per-vhost using it, so it can
			// do initial setup / allocations etc
			debug = __LINE__;
			break;
		default:
			break;
	}
	debug = __LINE__;
	// When you want to close a connection, you do it by returning -1 from a callback for that connection.
	return 0;
}

void *websocket::task_thread_routine(void *arg)
{
	// Start of error handling.
	pid_t tid = error_signals::GetThreadID();
	error_signals *error_thread = error_signals::GetThread(tid);
	if (error_thread == nullptr)
	{
	  error_thread = error_signals::AddThread(tid);
	}
	volatile int val = 0;
	if (error_thread != nullptr)
	{
	  error_thread->LineNumberStack = 0;
	  val = setjmp(error_thread->position);
	}
	if (val != 0)
	{
	  error_thread->DisplayErrorMessage(val);
	  // Exit the program.
	  error_signals::RemoveHandlers();
	  exit(val);
	}
	// End of error handling.
	Debug debug(__FILE__,__func__,__LINE__);
	tasks *chatroom_tasks = (tasks *)arg;	
	
	while (true) { // In the future, there might be a kill command to exit this loop.
		debug = __LINE__;
		chatroom_tasks->wait_for_tasks(1);
		debug = __LINE__;
		chatroom_tasks->do_tasks();
		debug = __LINE__;
	}
	error_thread->ReleaseThread(tid); // Error thread cleanup.
	pthread_exit(NULL);	 // This is how threads exit.
}


